package xxhttp

import (
	"bytes"
	"context"
	"fmt"
	"github.com/avast/retry-go"
	"github.com/duke-git/lancet/v2/fileutil"
	"github.com/duke-git/lancet/v2/strutil"
	"github.com/duke-git/lancet/v2/xerror"
	"github.com/gogf/gf/v2/container/gmap"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/valyala/fasthttp"
	"io"
	"mime/multipart"
	"net"
	"os"
	"time"
)

var (
	//外网
	defaultDialerWan = &fasthttp.TCPDialer{
		Concurrency:      4096,
		DNSCacheDuration: time.Hour,
		Resolver: &net.Resolver{
			PreferGo:     true,
			StrictErrors: false,
			Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
				d := net.Dialer{}
				return d.DialContext(ctx, "udp", "114.114.114.114:53")
			},
		},
	}
	//内网
	defaultDialerLan = &fasthttp.TCPDialer{Concurrency: 4096, DNSCacheDuration: time.Hour}
	httpClient       = creatHttpClientLan()
)

func creatHttpClientLan() fasthttp.Client {
	return fasthttp.Client{
		MaxConnsPerHost:     500,
		MaxIdleConnDuration: 10 * time.Second,
		MaxConnDuration:     20 * time.Second,
		ReadTimeout:         60 * time.Second,
		WriteTimeout:        60 * time.Second,
		MaxResponseBodySize: 1024 * 1024 * 10,
		MaxConnWaitTimeout:  time.Minute,
	}
}

const (
	globalTimeOut             = 30 * time.Second       //默认超时时间
	globalFileSize            = 5 * 1024 * 1024 * 1024 //下载文件大小限制
	HttpContentTypeJson       = "application/json"
	HttpContentTypeUrlEncoded = "application/x-www-form-urlencoded"
	HttpContentTypeStream     = "application/octet-stream"
)

// ReqMessage 返回
type ReqMessage struct {
	Code int
	Body string
	Err  error
}

type OpsHttpClient struct {
	Method      string
	IsWan       bool            //是否外网
	TimeOut     time.Duration   //超时时间
	ContentType string          //头类型
	HeaderMap   *gmap.StrStrMap //header Map
	Attempts    uint            //重试次数
	Delay       time.Duration   // 每次重试间隔10秒
	DownFileMax int
}

func NewHttpClient() *OpsHttpClient {
	http := &OpsHttpClient{
		//生成默认值
		fasthttp.MethodPost,
		false,
		globalTimeOut,
		HttpContentTypeJson,
		nil,
		3,
		10 * time.Second,
		globalFileSize,
	}
	return http
}
func (e *OpsHttpClient) HttpRequestRetry(postUrl string, data ...interface{}) (message ReqMessage) {
	retryFunc := func() error {
		message = e.HttpRequest(postUrl, data...)
		return message.Err
	}
	// 设置重试选项
	opts := []retry.Option{
		retry.Attempts(e.Attempts), // 最多重试3次
		retry.Delay(e.Delay),       // 每次重试间隔10秒
	}
	// 执行重试操作
	_ = retry.Do(retryFunc, opts...)
	return message
}

// HttpPostRequest post请求
func (e *OpsHttpClient) HttpRequest(postUrl string, data ...interface{}) (message ReqMessage) {
	var err error
	req := fasthttp.AcquireRequest()
	req.SetRequestURI(strutil.Trim(postUrl))
	req.Header.SetMethod(e.Method)
	req.Header.SetContentType(e.ContentType)
	if !g.IsNil(data) && len(data) > 0 {
		req.SetBodyString(BuildParams(e.ContentType, data[0]))
	}
	//添加自定义header
	if !g.IsNil(e.HeaderMap) {
		e.HeaderMap.Iterator(func(k string, v string) bool {
			req.Header.Set(k, v)
			return true
		})
	} //end if
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	defer fasthttp.ReleaseRequest(req)
	if e.IsWan {
		//外网DNS
		httpClient.Dial = defaultDialerWan.Dial
		err = httpClient.DoTimeout(req, resp, e.TimeOut)
	} else {
		//内网
		httpClient.Dial = defaultDialerLan.Dial
		err = httpClient.DoTimeout(req, resp, e.TimeOut)
	} //end else if
	if err != nil {
		message.Err = err
		message.Code = 500
	} else {
		message.Code = resp.StatusCode()
	}
	if resp.StatusCode() != 200 {
		message.Err = xerror.New(string(resp.Body()))
	}
	message.Body = string(resp.Body())
	return
}
func (e *OpsHttpClient) HttpUploadFile(postUrl string, filePath string, fieldName string, fileName string) (message ReqMessage) {
	// 读取文件
	file, err := os.Open(filePath)
	if err != nil {
		message.Code = 402
		message.Err = err
		return
	}
	defer file.Close()
	// 创建 multipart 写入器
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)
	// 添加文件（使用自定义文件名）
	part, err := writer.CreateFormFile(fieldName, fileName)
	if err != nil {
		message.Code = 402
		message.Err = err
		return
	}
	_, err = io.Copy(part, file)
	if err != nil {
		message.Code = 402
		message.Err = err
		return
	}
	// 关闭写入器
	err = writer.Close()
	if err != nil {
		message.Code = 402
		message.Err = err
		return
	}
	contentType := writer.FormDataContentType()
	req := fasthttp.AcquireRequest()
	req.SetRequestURI(strutil.Trim(postUrl))
	req.Header.SetMethod(fasthttp.MethodPost)
	req.Header.SetContentType(contentType)
	req.SetBody(buf.Bytes())
	//添加自定义header
	if !g.IsNil(e.HeaderMap) {
		e.HeaderMap.Iterator(func(k string, v string) bool {
			req.Header.Set(k, v)
			return true
		})
	} //end if
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	defer fasthttp.ReleaseRequest(req)
	if e.IsWan {
		//外网DNS
		httpClient.Dial = defaultDialerWan.Dial
		err = httpClient.DoTimeout(req, resp, e.TimeOut)
	} else {
		//内网
		httpClient.Dial = defaultDialerLan.Dial
		err = httpClient.DoTimeout(req, resp, e.TimeOut)
	} //end else if
	if err != nil {
		message.Err = err
		message.Code = 500
	} else {
		message.Code = resp.StatusCode()
	}
	if resp.StatusCode() != 200 {
		message.Err = xerror.New(string(resp.Body()))
	}
	message.Body = string(resp.Body())
	return
}
func (e *OpsHttpClient) HttpFileDownRequest(url, filePath string) error {
	var err error
	req := fasthttp.AcquireRequest()
	defer fasthttp.ReleaseRequest(req)
	req.SetRequestURI(strutil.Trim(url))
	req.Header.SetMethod("GET")
	resp := fasthttp.AcquireResponse()
	defer fasthttp.ReleaseResponse(resp)
	if e.IsWan {
		//外网DNS
		httpClient.Dial = defaultDialerWan.Dial
		httpClient.MaxResponseBodySize = globalFileSize
		err = httpClient.DoTimeout(req, resp, e.TimeOut)
	} else {
		//内网
		httpClient.Dial = defaultDialerLan.Dial
		httpClient.MaxResponseBodySize = globalFileSize
		err = httpClient.DoTimeout(req, resp, e.TimeOut)
	} //end else if
	if err != nil {
		return err
	}
	if resp.StatusCode() != 200 {
		return xerror.New(fmt.Sprintf("返回值为:%d", resp.StatusCode()))
	}
	return fileutil.WriteBytesToFile(filePath, resp.Body())
}
func (e *OpsHttpClient) HttpRequestRetryParam(postUrl string, attempts uint, delay time.Duration, data ...interface{}) (messgae ReqMessage) {
	retryFunc := func() error {
		messgae = e.HttpRequest(postUrl, data...)
		return messgae.Err
	}
	// 设置重试选项
	opts := []retry.Option{
		retry.Attempts(attempts), // 最多重试3次
		retry.Delay(delay),       // 每次重试间隔10秒
	}
	// 执行重试操作
	_ = retry.Do(retryFunc, opts...)
	return messgae
}
